//
//  WWManagedObject.m
//  Words2
//
//  Created by Mathieu on 11/07/06.
//  Copyright 2006 __MyCompanyName__. All rights reserved.
//

#import "WWManagedObject.h"


@implementation WWManagedObject
/**
    Returns a predicate to find another object that would be considered "equal"
    in attribute value(s).  This predicate is used by an object in one store to
    find a counterpart in another store to use for the purposes of copying an
    object.  We return NO here, but subclasses should override to implement
    something specific.
*/

- (NSPredicate *) predicateForSimilarObject {
    return [NSPredicate predicateWithFormat:NO];
}


/**
    Returns a object from one passed in stores that matches the current object.
    This implementation uses the predicateForExistingObject API to find an
    object that has matching attributes with the current one and returns it.
    If more than one object is found, the first one is returned.
*/

- (WWManagedObject *) similarObjectInContext:(NSManagedObjectContext *)context andStore:(id)store {

    // object to return
    WWManagedObject *object = nil;
    
    // attempt to find a matching object using the built-in predicate
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    [fetchRequest setEntity: [self entity]];
    [fetchRequest setPredicate: [self predicateForSimilarObject]];
    
    // set the affected store, if any
    if ( store ) {
        [fetchRequest setAffectedStores: [NSArray arrayWithObject: store]];
    }
    
    // get the array of matches
    NSArray *matches = [context executeFetchRequest:fetchRequest error:nil];
    
    // if we have a matching object
    if ( matches && [matches count] > 0 ) {
        object = [matches objectAtIndex: 0];
    }

    // clean up
    [fetchRequest release];
    return object;
}


/**
    Method to return a copy of the current object saved in the specified store 
    using the specified context.  (This is used to copy an object from one store
    to another.)  This implementation returns nothing, as subclasses must 
    override to implement the specifics of their copying requirements (for 
    example, if they must copy shallow or deep, use existing objects or create 
    new ones, etc.)
*/

- (WWManagedObject *) copyToContext: (NSManagedObjectContext *)context andStore:(id)store {
    return nil;
}


/**
    Copies all of the attribute values from the specified object into the
    current object.  No class comparison is attempted here (to avoid having to
    walk the inheritance tree), so care should be taken to copy values from an
    object with matching keys.
*/

- (void) copyAttributesFromObject: (NSManagedObject *)managedObject {

    // get the array of attribute keys and set the items accordingly
    NSArray *attributeKeys = [[self entity] attributeKeys];
    NSString *attributeName;
    
    // loop through the keys
    int i, count = [attributeKeys count];
    for ( i=0; i<count; i++ ) {
    
        // Get the attribute name, then copy the value
        attributeName = [attributeKeys objectAtIndex: i];
        [self setPrimitiveValue: [managedObject primitiveValueForKey: attributeName] forKey: attributeName];
    }
}


/**
    Generic accessor to add an object to a to-many relationship specified by a
    key.  Here we create a set with the changed objects, notify the framework
    of the change, and then add the object into the relationship.  This is the
    standard procedure for changing relationship values.
*/

- (void) addObject: (NSManagedObject *)object toToManyRelationshipForKey: (NSString *) key {

    NSSet *changedObjects = [[NSSet alloc] initWithObjects:&object count:1];

    [self willChangeValueForKey:key withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
    [[self primitiveValueForKey:key] addObject: object];
    [self didChangeValueForKey:key withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];

    [changedObjects release];
}


/**
    Copies the object at the end of the to-one relationship for the specified
    key on the specified object to the current object.  This implementation 
    will either use the existing object for the relationship, or will create a
    new instance of the object (copying all of its values) and set it into the
    current object.  This will also set the reverse relationship as necessary.
*/

- (void) copyToOneRelationshipForKey: (NSString *)key fromObject:(NSManagedObject *)managedObject useExisting:(BOOL)useExisting {

    id objectToCopy;

    // get the object to copy
    objectToCopy = [managedObject primitiveValueForKey: key];
    if ( objectToCopy ) {

        // get the context and store for this object
        NSManagedObjectContext *context = [self managedObjectContext];
        id store = [[self objectID] persistentStore];
        id newCopy;

        // if checking for existing
        if ( useExisting ) {
            newCopy = [objectToCopy similarObjectInContext:context andStore:store];
        }
        
        // if not checking existing, or there wasn't one, create a copy
        if ( newCopy == nil ) {
            newCopy = [objectToCopy copyToContext: context andStore: store];
        }

        // set the new copy
        [self setPrimitiveValue: newCopy forKey: key];
        
        // now check to see if we need to set the inverse
        NSRelationshipDescription *desc = [[[self entity] relationshipsByName] objectForKey: key];
        NSRelationshipDescription *inverseDesc = [desc inverseRelationship];
        if ( inverseDesc ) {
        
            // if the relationship is a to-many
            if ( [inverseDesc isToMany] ) {
                [newCopy addObject:self toToManyRelationshipForKey: [inverseDesc name]];
            }
            
            // otherwise set as a to-one
            else {
                [newCopy setPrimitiveValue:self forKey:[inverseDesc name]];
            }
        }
    }
}



/**
    Copies the objects at the end of the to-many relationship for the specified
    key on the specified object to the current object.  This implementation 
    will either use the existing objects for the relationship, or will create
    new instances of the objects (copying all of their values) and set them as
    the relationship for the current object.  This will also set the reverse 
    relationship as necessary.
*/

- (void) copyToManyRelationshipForKey: (NSString *)key fromObject: (NSManagedObject *)managedObject useExisting:(BOOL)useExisting {

    NSArray *relationshipObjects;
    
    // get the array of relationship objects
    relationshipObjects = [[managedObject primitiveValueForKey: key] allObjects];
    if ( relationshipObjects && [relationshipObjects count] > 0 ) {
    
        // get the context and the store for this object
        NSManagedObjectContext *context = [self managedObjectContext];
        id store = [[self objectID] persistentStore];
        
        // see if we need to look for an inverse
        NSRelationshipDescription *desc = [[[self entity] relationshipsByName] objectForKey: key];
        NSRelationshipDescription *inverseDesc = [desc inverseRelationship];
        BOOL isToMany = [inverseDesc isToMany];
        
        // interate the array of relationship objects
        int i, count = [relationshipObjects count];
        id objectToCopy = nil;
        id newCopy = nil;

        for ( i = 0; i < count; i++ ) {

            // get the object
            objectToCopy = [relationshipObjects objectAtIndex:i];

            // if checking for existing
            if ( useExisting ) {
                newCopy = [objectToCopy similarObjectInContext:context andStore:store];
            }
            
            // if not checking existing, or there wasn't one, create a copy
            if ( newCopy == nil ) {
                newCopy = [objectToCopy copyToContext:context andStore:store];
            }

            // add the value to the relationship
            [self addObject:newCopy toToManyRelationshipForKey:key];

            // process the inverse, if we need to
            if ( inverseDesc ) {
            
                // to-many
                if ( isToMany ) {
                    [newCopy addObject:self toToManyRelationshipForKey: [inverseDesc name]];
                }
                
                // otherwise
                else {
                    [newCopy setPrimitiveValue:self forKey:[inverseDesc name]];
                }
            }
            
            // reset 
            newCopy = nil;
        }
    }

    // more cleanup
    relationshipObjects = nil;
}

@end
